// @vitest-environment jsdom
//
// `/[slug]` per-release page — `getStaticPaths` (one path per release),
// `loader` (look up the release, shiki-highlight it, `notFound()` on a miss),
// `meta(loaderData)`, and the render (which injects the highlighted HTML). We
// mock `../lib/releases` (avoids the node-only glob) and `@voltro/changelog`
// (`highlightRelease` pulls in shiki) and `@voltro/web` (`notFound`).

import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { act, createElement, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
// Type-only import — erased at runtime, so it does NOT pull @voltro/changelog's
// node-only glob (the value module stays mocked below). Using the real Release
// type keeps the mock in lockstep with the framework's shape (tags: ReleaseTag[],
// draft, body, …) so the test typechecks against what the page actually consumes.
import type { Release } from '@voltro/changelog'

const releases: ReadonlyArray<Release> = [
  {
    version: '0.1.0',
    releasedAt: '2026-01-15',
    slug: 'v0-1-0',
    tags: ['feature'],
    title: 'First release',
    summary: 'The initial cut.',
    draft: false,
    body: 'The initial cut.',
    html: '<p>plain</p>',
  },
]

const releaseBySlug = (slug: string): Release | undefined => releases.find((r) => r.slug === slug)

// `../../lib/releases` — the path the PAGE imports. This read `../lib/releases`,
// which from `src/pages/[slug]/` resolves to `src/pages/lib/releases`: a module
// that does not exist, so the mock registered against nothing and the REAL
// `src/lib/releases.ts` loaded. A wrong `vi.mock` specifier is silent in vitest
// — it fails later and elsewhere, here as "No `loadReleases` export is defined
// on the `@voltro/changelog` mock", because the real module calls it at import
// time. The sibling `[locale]/mirrors.test.tsx`, at the same depth, had it right.
vi.mock('../../lib/releases', () => ({ releases, releaseBySlug }))
// `highlightRelease` runs shiki in node; the page only needs it to return a
// (possibly re-rendered) release — the identity, tagged so we can assert it ran.
vi.mock('@voltro/changelog', () => ({
  highlightRelease: async (r: Release) => ({ ...r, html: `<pre>${r.title}</pre>` }),
}))

const loaderData = vi.fn<() => { release: Release }>()
class NotFoundError extends Error {}
vi.mock('@voltro/web', () => ({
  useLoaderData: () => loaderData(),
  notFound: (detail?: string) => {
    throw new NotFoundError(detail)
  },
  // `withLocalePrefix` (lib/locale) reads useLocation; the page pins the locale
  // via useLocale, so any bare path is fine here.
  useLocation: () => '/',
}))
// The back-link chrome comes from @voltro/i18n; stub it (useLocale → en,
// <T> passes children through).
vi.mock('@voltro/i18n', () => ({
  defineCatalog: <T,>(c: T): T => c,
  defineLocale:
    () =>
    <T,>(c: T): T =>
      c,
  useLocale: () => 'en',
  T: ({ id }: { id: string }) => id,
}))

const { default: ReleasePage, getStaticPaths, loader, meta } = await import('./page')

;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true

let container: HTMLDivElement
let root: Root

const render = (node: ReactNode): void => {
  container = document.createElement('div')
  document.body.appendChild(container)
  act(() => {
    root = createRoot(container)
    root.render(node)
  })
}

beforeEach(() => {
  loaderData.mockReset()
})

afterEach(() => {
  if (root) act(() => root.unmount())
  container?.remove()
  document.body.innerHTML = ''
})

describe('changelog [slug] — getStaticPaths', () => {
  test('enumerates one path per release slug', async () => {
    const paths = await getStaticPaths()
    expect(paths.map((p) => p.params.slug)).toEqual(['v0-1-0'])
  })
})

describe('changelog [slug] — loader', () => {
  test('resolves the release and runs it through highlightRelease', async () => {
    const { release } = await loader({ params: { slug: 'v0-1-0' } } as never)
    expect(release.slug).toBe('v0-1-0')
    expect(release.html).toBe('<pre>First release</pre>')
  })

  test('throws notFound for an unknown slug', async () => {
    await expect(loader({ params: { slug: 'nope' } } as never)).rejects.toBeInstanceOf(
      NotFoundError,
    )
  })
})

describe('changelog [slug] — meta', () => {
  test('builds the per-release title + description from the loader data', () => {
    const m = meta({ loaderData: { release: releases[0]! } })
    expect(m.title).toContain('First release')
    expect(m.description).toBe('The initial cut.')
  })
})

describe('changelog [slug] — render', () => {
  test('renders the title, version, and injects the highlighted HTML body', () => {
    loaderData.mockReturnValue({ release: { ...releases[0]!, html: '<p>the body html</p>' } })
    render(createElement(ReleasePage))
    expect(container.querySelector('h1')?.textContent).toBe('First release')
    expect(container.textContent).toContain('v0.1.0')
    expect(container.innerHTML).toContain('the body html')
  })
})
